﻿using System;
using System.Threading.Tasks;
using DirectCanvas;
using DirectCanvas.Brushes;
using DirectCanvas.Misc;
using DirectCanvas.Multimedia;
using DirectCanvas.Shapes;
using DirectCanvas.Transforms;
using DirectCanvas.Scenes;

namespace WindowsFormsTest.Demos.CompositorDemo
{
    class CompositorScene : Scene
    {
        private readonly Presenter m_presenter;

        private const int SCENE_WIDTH = 1280;
        private const int SCENE_HEIGHT = 720;

        private const int COMPOSITED_ELEMENT_WIDTH = 10;
        private const int COMPOSITED_ELEMENT_HEIGHT = 10;

        private const string VIDEO_FILENAME = @"C:\Users\Public\Videos\Sample Videos\Wildlife.wmv";

        private DrawingLayer m_mainLayer;
        private DrawingLayer m_opacityMask;
        private MediaPlayer m_player;
        private VideoBrush m_videoBrush;

        private VisualElement[] m_visualElements;

        private bool m_mousedown;
        private PointF m_mousePosition;

        public CompositorScene(Presenter presenter)
        {
            m_presenter = presenter;

            /* Initialize our scenes resources */
            InitializeResources();
        }

        public override void SetInputState(int inputId, InputStatus status, PointF position)
        {
            m_mousedown = status == InputStatus.Down;
            m_mousePosition = position;
        }

        #region Render Methods
        /// <summary>
        /// Draws the video on to the main layer, using the opacity mask
        /// </summary>
        private void RenderVideoAndOpacityMask(DrawingLayer layer)
        {
            layer.BeginDraw();

            /* Clear the old drawing layer*/
            layer.Clear();

            /* Scale the video brush to match the size of the layer */
            m_videoBrush.Transform = new ScaleTransform
                                     {
                                         ScaleX = (float)layer.Width / m_player.NaturalSize.Width,
                                         ScaleY = (float)layer.Height / m_player.NaturalSize.Height
                                     };


            /* Draws the contents of the video brush, using the given opacity mask, onto the layer. */
            layer.FillOpacityMask(m_videoBrush,
                                  m_opacityMask,
                                  new RectangleF(0, 0, layer.Width, layer.Height),
                                  new RectangleF(0, 0, layer.Width, layer.Height));
            layer.EndDraw();
        }

        private void ComposeToPresenterLayer()
        {
            /* Clear our drawing layer */
            m_presenter.Clear();
           
            m_presenter.BeginCompose(BlendStateMode.AlphaBlend);

            /* A little trick for some perf speed up for large numbers of elements */
            Parallel.For(0, m_visualElements.Length, i => m_visualElements[i].Update(m_mousedown, m_mousePosition));

            /* Loop over each of our visual elements.  Each visual element simply
             * just holds state information (like location, rotation amount, etc).
             * We run the "Compose" helper method for each element */
            for (int i = 0; i < m_visualElements.Length; i++)
            {
                m_visualElements[i].Compose(m_presenter, m_mainLayer);
            }

            m_presenter.EndCompose();
        }

        public override void Render()
        {
            RenderVideoAndOpacityMask(m_mainLayer);
            ComposeToPresenterLayer();
        }
#endregion

#region Initialization Methods
        private void InitializeResources()
        {
            m_mainLayer = m_presenter.Factory.CreateDrawingLayer(SCENE_WIDTH, SCENE_HEIGHT);
            m_opacityMask = CreateOpacityMask();
            
            InitMediaPlayer();
            
            InitializeVisualElements();
        }

        private void InitializeVisualElements()
        {
            int elementsAcross = SCENE_WIDTH / COMPOSITED_ELEMENT_WIDTH;
            int elementsDown = SCENE_HEIGHT / COMPOSITED_ELEMENT_HEIGHT;

            m_visualElements = new VisualElement[elementsAcross * elementsDown];

            int elementCount = 0;

            for (int x = 0; x < elementsAcross; x++)
            {
                for (int y = 0; y < elementsDown; y++)
                {
                    var element = new VisualElement(new RectangleF(x * COMPOSITED_ELEMENT_WIDTH,
                                                                   y * COMPOSITED_ELEMENT_HEIGHT,
                                                                   COMPOSITED_ELEMENT_WIDTH,
                                                                   COMPOSITED_ELEMENT_HEIGHT));

                    m_visualElements[elementCount] = element;

                    elementCount++;
                }
            }
        }

        private void InitMediaPlayer()
        {
            m_player = m_presenter.Factory.CreateMediaPlayer();
            m_player.Open(VIDEO_FILENAME);
            //m_player.Play();

            m_videoBrush = m_presenter.Factory.CreateVideoBrush(m_player);
            m_videoBrush.HorizontalExtendMode = ExtendMode.Wrap;
            m_videoBrush.VerticalExtendMode = ExtendMode.Wrap;
        }

        public override void ActivateScene()
        {
            m_player.Play();
        }

        public override void DeactivateScene()
        {
            m_player.Pause();
        }

        private DrawingLayer CreateOpacityMask()
        {
            /* Create a drawing layer the size of our scene */
            var opacityMaskLayer = m_presenter.Factory.CreateDrawingLayer(SCENE_WIDTH, SCENE_HEIGHT);

            var opacityMaskTemplateLayer = CreateOpacityMaskTemplate();

            /* Create a bitmap brush */
            var opacityMaskTemplateBitmapBrush = m_presenter.Factory.CreateDrawingLayerBrush(opacityMaskTemplateLayer);

            /* Setting the extend mode to wrap will cause a tile of the bitmap to happen */
            opacityMaskTemplateBitmapBrush.HorizontalExtendMode = ExtendMode.Wrap;
            opacityMaskTemplateBitmapBrush.VerticalExtendMode = ExtendMode.Wrap;

            /* Queue the drawing */
            opacityMaskLayer.BeginDraw();

            /* Since the fill area we are passing is larger than the bitmap, we get tiling */
            opacityMaskLayer.FillRectangle(opacityMaskTemplateBitmapBrush,
                                           new RectangleF(0, 0, opacityMaskLayer.Width, opacityMaskLayer.Height));

            /* Flush the drawing commands */
            opacityMaskLayer.EndDraw();

            /* Free our temporary resources */
            opacityMaskTemplateLayer.Dispose();

            return opacityMaskLayer;
        }

        private DrawingLayer CreateOpacityMaskTemplate()
        {
            /* This is a temporary drawing layer that we will draw just a single shape to
            * but we will tile it with a bitmap brush on to the opacityMaskLayer */
            var opacityMaskTemplateLayer = m_presenter.Factory.CreateDrawingLayer(COMPOSITED_ELEMENT_WIDTH, COMPOSITED_ELEMENT_HEIGHT);

            /* The brush we will use to fill a single shape on our template layer */
            var solidColorBrush = m_presenter.Factory.CreateSolidColorBrush(new Color4(1, 1, 1, 1));

            var shapeArea = new RectangleF(0, 0, COMPOSITED_ELEMENT_WIDTH, COMPOSITED_ELEMENT_HEIGHT);

            /* Queue the drawing */
            opacityMaskTemplateLayer.BeginDraw();

            opacityMaskTemplateLayer.FillEllipse(solidColorBrush,
                                                 new Ellipse(shapeArea.Center, shapeArea.Width / 3, shapeArea.Height / 3));

            /* Flush the drawing commands */
            opacityMaskTemplateLayer.EndDraw();

            solidColorBrush.Dispose();

            return opacityMaskTemplateLayer;
        }
#endregion
    }
}
